@@ -11,11 +11,11 @@ module Oauthable |
||
11 | 11 |
true |
12 | 12 |
end |
13 | 13 |
|
14 |
- def valid_services(current_user) |
|
14 |
+ def valid_services_for(user) |
|
15 | 15 |
if valid_oauth_providers == :all |
16 |
- current_user.available_services |
|
16 |
+ user.available_services |
|
17 | 17 |
else |
18 |
- current_user.available_services.where(provider: valid_oauth_providers) |
|
18 |
+ user.available_services.where(provider: valid_oauth_providers) |
|
19 | 19 |
end |
20 | 20 |
end |
21 | 21 |
|
@@ -25,11 +25,11 @@ module TwitterConcern |
||
25 | 25 |
end |
26 | 26 |
|
27 | 27 |
def twitter_oauth_token |
28 |
- self.service.token |
|
28 |
+ service.token |
|
29 | 29 |
end |
30 | 30 |
|
31 | 31 |
def twitter_oauth_token_secret |
32 |
- self.service.secret |
|
32 |
+ service.secret |
|
33 | 33 |
end |
34 | 34 |
|
35 | 35 |
def twitter |
@@ -1,5 +1,4 @@ |
||
1 | 1 |
class ServicesController < ApplicationController |
2 |
- |
|
3 | 2 |
def index |
4 | 3 |
@services = current_user.services.page(params[:page]) |
5 | 4 |
|
@@ -44,7 +44,7 @@ class Agent < ActiveRecord::Base |
||
44 | 44 |
after_save :possibly_update_event_expirations |
45 | 45 |
|
46 | 46 |
belongs_to :user, :inverse_of => :agents |
47 |
- belongs_to :service |
|
47 |
+ belongs_to :service, :inverse_of => :agents |
|
48 | 48 |
has_many :events, -> { order("events.id desc") }, :dependent => :delete_all, :inverse_of => :agent |
49 | 49 |
has_one :most_recent_event, :inverse_of => :agent, :class_name => "Event", :order => "events.id desc" |
50 | 50 |
has_many :logs, -> { order("agent_logs.id desc") }, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog" |
@@ -59,7 +59,7 @@ module Agents |
||
59 | 59 |
end |
60 | 60 |
|
61 | 61 |
def check |
62 |
- self.service.prepare_request |
|
62 |
+ service.prepare_request |
|
63 | 63 |
reponse = HTTParty.get request_url, request_options.merge(query_parameters) |
64 | 64 |
memory[:last_run] = Time.now.utc.iso8601 |
65 | 65 |
if last_check_at != nil |
@@ -72,11 +72,11 @@ module Agents |
||
72 | 72 |
|
73 | 73 |
private |
74 | 74 |
def request_url |
75 |
- "https://basecamp.com/#{URI.encode(self.service.options[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json" |
|
75 |
+ "https://basecamp.com/#{URI.encode(service.options[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json" |
|
76 | 76 |
end |
77 | 77 |
|
78 | 78 |
def request_options |
79 |
- {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => "Bearer \"#{self.service.token}\""}} |
|
79 |
+ {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => "Bearer \"#{service.token}\""}} |
|
80 | 80 |
end |
81 | 81 |
|
82 | 82 |
def query_parameters |
@@ -1,17 +1,22 @@ |
||
1 | 1 |
class Service < ActiveRecord::Base |
2 |
+ PROVIDER_TO_ENV_MAP = {'37signals' => 'THIRTY_SEVEN_SIGNALS'} |
|
3 |
+ |
|
2 | 4 |
attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options |
3 | 5 |
|
4 | 6 |
serialize :options, Hash |
5 | 7 |
|
6 |
- belongs_to :user |
|
7 |
- has_many :agents |
|
8 |
+ belongs_to :user, :inverse_of => :services |
|
9 |
+ has_many :agents, :inverse_of => :service |
|
8 | 10 |
|
9 | 11 |
validates_presence_of :user_id, :provider, :name, :token |
10 | 12 |
|
11 | 13 |
before_destroy :disable_agents |
12 | 14 |
|
15 |
+ scope :available_to_user, lambda { |user| where("services.user_id = ? or services.global = true", user.id) } |
|
16 |
+ scope :by_name, lambda { |dir = 'desc'| order("services.name #{dir}") } |
|
17 |
+ |
|
13 | 18 |
def disable_agents |
14 |
- self.agents.each do |agent| |
|
19 |
+ agents.each do |agent| |
|
15 | 20 |
agent.service_id = nil |
16 | 21 |
agent.disabled = true |
17 | 22 |
agent.save!(validate: false) |
@@ -24,52 +29,55 @@ class Service < ActiveRecord::Base |
||
24 | 29 |
end |
25 | 30 |
|
26 | 31 |
def prepare_request |
27 |
- if self.expires_at && Time.now > self.expires_at |
|
28 |
- self.refresh_token! |
|
32 |
+ if expires_at && Time.now > expires_at |
|
33 |
+ refresh_token! |
|
29 | 34 |
end |
30 | 35 |
end |
31 | 36 |
|
32 | 37 |
def refresh_token! |
33 | 38 |
response = HTTParty.post(endpoint, query: { |
34 | 39 |
type: 'refresh', |
35 |
- client_id: ENV["#{provider_to_env}_OAUTH_KEY"], |
|
36 |
- client_secret: ENV["#{provider_to_env}_OAUTH_SECRET"], |
|
37 |
- refresh_token: self.refresh_token |
|
40 |
+ client_id: oauth_key, |
|
41 |
+ client_secret: oauth_secret, |
|
42 |
+ refresh_token: refresh_token |
|
38 | 43 |
}) |
39 | 44 |
data = JSON.parse(response.body) |
40 |
- self.update(expires_at: Time.now + data['expires_in'], token: data['access_token'], refresh_token: data['refresh_token'].presence || self.refresh_token) |
|
41 |
- end |
|
42 |
- |
|
43 |
- def self.initialize_or_update_via_omniauth(omniauth) |
|
44 |
- case omniauth['provider'] |
|
45 |
- when 'twitter' |
|
46 |
- find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service| |
|
47 |
- service.assign_attributes(token: omniauth['credentials']['token'], secret: omniauth['credentials']['secret']) |
|
48 |
- end |
|
49 |
- when 'github' |
|
50 |
- find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service| |
|
51 |
- service.assign_attributes(token: omniauth['credentials']['token']) |
|
52 |
- end |
|
53 |
- when '37signals' |
|
54 |
- find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['name']).tap do |service| |
|
55 |
- service.assign_attributes(token: omniauth['credentials']['token'], |
|
56 |
- refresh_token: omniauth['credentials']['refresh_token'], |
|
57 |
- expires_at: Time.at(omniauth['credentials']['expires_at']), |
|
58 |
- options: {user_id: omniauth['extra']['accounts'][0]['id']}) |
|
59 |
- end |
|
60 |
- else |
|
61 |
- false |
|
62 |
- end |
|
45 |
+ update(expires_at: Time.now + data['expires_in'], token: data['access_token'], refresh_token: data['refresh_token'].presence || refresh_token) |
|
63 | 46 |
end |
64 | 47 |
|
65 |
- private |
|
66 | 48 |
def endpoint |
67 | 49 |
client_options = "OmniAuth::Strategies::#{OmniAuth::Utils.camelize(self.provider)}".constantize.default_options['client_options'] |
68 | 50 |
URI.join(client_options['site'], client_options['token_url']) |
69 | 51 |
end |
70 | 52 |
|
71 |
- @@provider_to_env_map = {'37signals' => 'THIRTY_SEVEN_SIGNALS'} |
|
72 | 53 |
def provider_to_env |
73 |
- @@provider_to_env_map[self.provider].presence || self.provider.upcase |
|
54 |
+ PROVIDER_TO_ENV_MAP[provider].presence || provider.upcase |
|
55 |
+ end |
|
56 |
+ |
|
57 |
+ def oauth_key |
|
58 |
+ ENV["#{provider_to_env}_OAUTH_KEY"] |
|
59 |
+ end |
|
60 |
+ |
|
61 |
+ def oauth_secret |
|
62 |
+ ENV["#{provider_to_env}_OAUTH_SECRET"] |
|
63 |
+ end |
|
64 |
+ |
|
65 |
+ def self.provider_specific_options(omniauth) |
|
66 |
+ case omniauth['provider'] |
|
67 |
+ when '37signals' |
|
68 |
+ { user_id: omniauth['extra']['accounts'][0]['id'] } |
|
69 |
+ else |
|
70 |
+ {} |
|
71 |
+ end |
|
72 |
+ end |
|
73 |
+ |
|
74 |
+ def self.initialize_or_update_via_omniauth(omniauth) |
|
75 |
+ find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname'] || omniauth['info']['name']).tap do |service| |
|
76 |
+ service.assign_attributes token: omniauth['credentials']['token'], |
|
77 |
+ secret: omniauth['credentials']['secret'], |
|
78 |
+ refresh_token: omniauth['credentials']['refresh_token'], |
|
79 |
+ expires_at: omniauth['credentials']['expires_at'] && Time.at(omniauth['credentials']['expires_at']), |
|
80 |
+ options: provider_specific_options(omniauth) |
|
81 |
+ end |
|
74 | 82 |
end |
75 | 83 |
end |
@@ -27,10 +27,10 @@ class User < ActiveRecord::Base |
||
27 | 27 |
has_many :agents, -> { order("agents.created_at desc") }, :dependent => :destroy, :inverse_of => :user |
28 | 28 |
has_many :logs, :through => :agents, :class_name => "AgentLog" |
29 | 29 |
has_many :scenarios, :inverse_of => :user, :dependent => :destroy |
30 |
- has_many :services, -> { order("services.name")}, :dependent => :destroy |
|
30 |
+ has_many :services, -> { by_name('asc') }, :dependent => :destroy |
|
31 | 31 |
|
32 | 32 |
def available_services |
33 |
- Service.where("user_id = ? or global = true", self.id).order("services.name desc") |
|
33 |
+ Service.available_to_user(self).by_name |
|
34 | 34 |
end |
35 | 35 |
|
36 | 36 |
# Allow users to login via either email or username. |
@@ -34,7 +34,7 @@ |
||
34 | 34 |
<% if @agent.try(:oauthable?) %> |
35 | 35 |
<div class="form-group type-select"> |
36 | 36 |
<%= f.label :service %> |
37 |
- <%= f.select :service_id, options_for_select(@agent.valid_services(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, @agent.service_id),{}, class: 'form-control' %> |
|
37 |
+ <%= f.select :service_id, options_for_select(@agent.valid_services_for(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, @agent.service_id), {}, class: 'form-control' %> |
|
38 | 38 |
</div> |
39 | 39 |
<% end %> |
40 | 40 |
</div> |
@@ -120,12 +120,13 @@ |
||
120 | 120 |
</div> |
121 | 121 |
<% end %> |
122 | 122 |
</div> |
123 |
+ |
|
123 | 124 |
<% if agent_diff.requires_service? %> |
124 | 125 |
<div class='row'> |
125 | 126 |
<div class='col-md-4'> |
126 | 127 |
<div class="form-group type-select"> |
127 | 128 |
<%= label_tag "scenario_import[merges][#{index}][service_id]", 'Service' %> |
128 |
- <%= select_tag "scenario_import[merges][#{index}][service_id]", options_for_select(agent_diff.agent_instance.valid_services(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, agent_diff.service_id.try(:current)), class: 'form-control' %> |
|
129 |
+ <%= select_tag "scenario_import[merges][#{index}][service_id]", options_for_select(agent_diff.agent_instance.valid_services_for(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, agent_diff.service_id.try(:current)), class: 'form-control' %> |
|
129 | 130 |
</div> |
130 | 131 |
</div> |
131 | 132 |
</div> |
@@ -7,7 +7,7 @@ |
||
7 | 7 |
</h2> |
8 | 8 |
</div> |
9 | 9 |
<p> |
10 |
- Before you can authenticate with a service, you need to set it up. Have a look at the |
|
10 |
+ Before you can authenticate with a service, you need to set it up. Have a look at the Huginn |
|
11 | 11 |
<%= link_to 'wiki', 'https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications', target: :_blank %> |
12 | 12 |
for guidance. |
13 | 13 |
</p> |
@@ -33,9 +33,9 @@ |
||
33 | 33 |
<td> |
34 | 34 |
<div class="btn-group btn-group-xs"> |
35 | 35 |
<% if service.global %> |
36 |
- <%= link_to 'Make private', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to remove the access to this service for every user?'}, class: "btn btn-default" %> |
|
36 |
+ <%= link_to 'Make private', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to remove access to your data on this service for other users?'}, class: "btn btn-default" %> |
|
37 | 37 |
<% else %> |
38 |
- <%= link_to 'Make global', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to grant every user access to this service?'}, class: "btn btn-default" %> |
|
38 |
+ <%= link_to 'Make global', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to grant every user on this system access to your data on this service?'}, class: "btn btn-default" %> |
|
39 | 39 |
<% end %> |
40 | 40 |
<%= link_to 'Delete', service_path(service), method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-default btn-danger" %> |
41 | 41 |
</div> |
@@ -40,7 +40,7 @@ class MigrateAgentsToServiceAuthentication < ActiveRecord::Migration |
||
40 | 40 |
if Agent.where(type: ['Agents::BasecampAgent']).count > 0 |
41 | 41 |
puts <<-EOF.strip_heredoc |
42 | 42 |
|
43 |
- Your Basecamp agents can not be migrated automatically. You need to manually register an application with 37signals and authenticate huginn to use it. |
|
43 |
+ Your Basecamp agents can not be migrated automatically. You need to manually register an application with 37signals and authenticate Huginn to use it. |
|
44 | 44 |
Have a look at the wiki (https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications) if you need help. |
45 | 45 |
|
46 | 46 |
|
@@ -41,17 +41,18 @@ describe ServicesController do |
||
41 | 41 |
end |
42 | 42 |
|
43 | 43 |
describe "accepting a callback url" do |
44 |
- it "should update the users credentials" do |
|
44 |
+ it "should update the user's credentials" do |
|
45 | 45 |
expect { |
46 | 46 |
get :callback, provider: 'twitter' |
47 | 47 |
}.to change { users(:bob).services.count }.by(1) |
48 | 48 |
end |
49 | 49 |
|
50 |
- it "should not work with an unknown provider" do |
|
50 |
+ it "should work with an unknown provider (for now)" do |
|
51 | 51 |
request.env["omniauth.auth"]['provider'] = 'unknown' |
52 | 52 |
expect { |
53 | 53 |
get :callback, provider: 'unknown' |
54 |
- }.to change { users(:bob).services.count }.by(0) |
|
54 |
+ }.to change { users(:bob).services.count }.by(1) |
|
55 |
+ users(:bob).services.first.provider.should == 'unknown' |
|
55 | 56 |
end |
56 | 57 |
end |
57 | 58 |
end |
@@ -16,14 +16,14 @@ shared_examples_for Oauthable do |
||
16 | 16 |
@agent.oauthable?.should == true |
17 | 17 |
end |
18 | 18 |
|
19 |
- describe "valid_services" do |
|
19 |
+ describe "valid_services_for" do |
|
20 | 20 |
it "should return all available services without specifying valid_oauth_providers" do |
21 | 21 |
@agent = Agents::OauthableTestAgent.new |
22 |
- @agent.valid_services(users(:bob)).collect(&:id).sort.should == [services(:generic), services(:global)].collect(&:id).sort |
|
22 |
+ @agent.valid_services_for(users(:bob)).collect(&:id).sort.should == [services(:generic), services(:global)].collect(&:id).sort |
|
23 | 23 |
end |
24 | 24 |
|
25 | 25 |
it "should filter the services based on the agent defaults" do |
26 |
- @agent.valid_services(users(:bob)).to_a.should == Service.where(provider: @agent.valid_oauth_providers) |
|
26 |
+ @agent.valid_services_for(users(:bob)).to_a.should == Service.where(provider: @agent.valid_oauth_providers) |
|
27 | 27 |
end |
28 | 28 |
end |
29 | 29 |
end |